Skip to content

feat: Accept synchronous hooks on the async client - #548

Merged
jsonbailey merged 3 commits into
mainfrom
jb/sdk-3077/sync-hooks-async-client
Nov 2, 2026
Merged

jsonbailey merged 3 commits into
mainfrom
jb/sdk-3077/sync-hooks-async-client

Conversation

@jsonbailey

@jsonbailey jsonbailey commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

feat: Remove the experimental AsyncHook class

Warning

Breaking change to the experimental async API. Code that subclasses AsyncHook or passes AsyncHook instances to AsyncLDClient must change to use ldclient.hook.Hook. The async API is experimental, so this is not marked with ! and does not need a major version. Edit the release notes to call out the break for async users.

Summary

AsyncLDClient now uses the same ldclient.hook.Hook as LDClient, so existing integrations such as the OpenTelemetry tracing hook work with the async client. Before this change, the async client accepted only AsyncHook and ignored sync hooks.

  • The async client runs the hook stages synchronously in the caller's task. A span or context variable set in the before stage stays current until the after stage.
  • AsyncHook is removed. The async API is experimental.
  • If an evaluation is cancelled or raises, the after stages still run, with an error detail and the default value. The exception is then re-raised. A tracing hook can then end its span.
  • AsyncPlugin keeps its async register(). Its get_hooks() now returns Hook instances.
  • AsyncConfig and add_hook() ignore items of the wrong type, the same as the sync client.
  • The async contract-test service uses the sync PostingHook.

Note

Overview
Breaking change for the experimental async client: AsyncHook is removed. AsyncLDClient and AsyncConfig now take the same Hook type as LDClient, so shared integrations (e.g. OpenTelemetry) work without a separate async hook API.

Hook before/after stages run synchronously in the caller’s task (not awaited). Context set in before_evaluation stays visible through the evaluation and is cleared in after_evaluation, which matters for contextvars and tracing under concurrent variation() calls.

When evaluation raises or is cancelled, after hooks still run with an error EvaluationDetail (default value + EXCEPTION reason) before the exception propagates, so hooks can tear down spans/context.

add_hook() and config hooks filtering match the sync client (ignore non-Hook types instead of raising on sync hooks). AsyncPlugin.get_hooks() returns Hook instances; contract tests drop AsyncPostingHook and register PostingHook via config.

Reviewed by Cursor Bugbot for commit 17e8352. Bugbot is set up for automated code reviews on this repo. Configure here.

AsyncLDClient now takes the same ldclient.hook.Hook as LDClient, so
integrations such as the OpenTelemetry tracing hook work with it. The
hook stages run synchronously in the caller's task, so a span made
current in the before stage stays current until the after stage.

AsyncHook is removed. AsyncPlugin keeps its async register(), and its
get_hooks() returns Hook instances. AsyncConfig now raises TypeError
for a hook that is not a Hook or a plugin that is not an AsyncPlugin,
which matches add_hook().
Match the sync client: AsyncConfig and add_hook() ignore items of the wrong type. The async contract-test service uses the sync PostingHook. Remove redundant docstring notes.
The after stages get an error detail with the default value, and the exception is re-raised. A hook such as the OpenTelemetry tracing hook can then detach its context and end its span.
@jsonbailey
jsonbailey force-pushed the jb/sdk-3077/sync-hooks-async-client branch from 915b8d6 to 17e8352 Compare November 2, 2026 14:24
@jsonbailey
jsonbailey marked this pull request as ready for review November 2, 2026 15:06
@jsonbailey
jsonbailey requested a review from a team as a code owner November 2, 2026 15:06
@jsonbailey
jsonbailey merged commit 2457311 into main Nov 2, 2026
18 checks passed
@jsonbailey
jsonbailey deleted the jb/sdk-3077/sync-hooks-async-client branch November 2, 2026 16:33
jsonbailey added a commit that referenced this pull request Nov 2, 2026
🤖 I have created a release *beep* *boop*
---


##
[9.19.0](9.18.4...9.19.0)
(2026-11-02)


### Breaking changes to the experimental async client

The async client is experimental, so these changes do not cause a major
version bump. If you use `AsyncLDClient` with hooks or plugins, update
your code:

* Remove the experimental `AsyncHook` class. The async client now uses
the same synchronous `Hook` class as the sync client. Change each
`AsyncHook` subclass to subclass `Hook`, and change its `async def`
stages to `def`. The stages run on the event loop, so they must not
block.
([#548](#548))
([2457311](2457311))
* `AsyncPlugin.get_hooks` now returns a list of `Hook` instances.
`AsyncPlugin.register` stays async.
([#548](#548))
([2457311](2457311))


### Features

* Accept synchronous hooks on the async client
([#548](#548))
([2457311](2457311))


### Bug Fixes

* Run after-evaluation hooks when an async evaluation is cancelled or
raises
([2457311](2457311))

---
This PR was generated with [Release
Please](https://github.com/googleapis/release-please). See
[documentation](https://github.com/googleapis/release-please#release-please).

<!-- CURSOR_SUMMARY -->
---

> [!NOTE]
> **Overview**
> **Release 9.19.0** — bumps the package version from **9.18.4** to
**9.19.0** in `pyproject.toml`, `ldclient/version.py`,
`.release-please-manifest.json`, and the provenance verification example
in `PROVENANCE.md`, and adds the **9.19.0** section to `CHANGELOG.md`.
> 
> What ships in this release (documented in the new changelog entry, not
in this diff’s code):
> 
> The **experimental async client** unifies on the sync **`Hook`** API:
**`AsyncHook` is removed**, **`AsyncPlugin.get_hooks`** returns
**`Hook`** instances ( **`AsyncPlugin.register`** stays async), and the
async client **accepts synchronous hooks**. Hook stage methods should be
plain **`def`** (non-blocking on the event loop). A bug fix ensures
**after-evaluation hooks run** when an async evaluation is **cancelled
or raises**.
> 
> <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
54a6f6a. Bugbot is set up for automated
code reviews on this repo. Configure
[here](https://www.cursor.com/dashboard/bugbot).</sup>
<!-- /CURSOR_SUMMARY -->

---------

Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: jsonbailey <jbailey@launchdarkly.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants